#! /usr/bin/env node

// @ts-check
const { launch } = require('puppeteer')
const moment = require('moment')
const minimist = require('minimist')
const config = require('./config')
const { withTimeoutRetry, wait } = require('./utils')
const { notify } = require('./helpers')

const taskStateMap = {
  await: 'task-wait',
  onGoing: 'task-doing'
}

const argv = minimist(process.argv.slice(2))

let browser
let page

const taskTpl = config.template


////////////////////////////////// HELPERS //////////////////////////////////
const init = async () => {
  const headless = typeof argv.headed === 'undefined' ? true : !argv.headed
  browser = await launch({ headless })
  page = await browser.newPage()
  await page.goto('https://pm.hailiangtong.com/index.php?m=user&f=login&referer=Lw==')
  await page.type('#account', config.username)
  await page.type('[name="password"]', config.password)
  await page.click('#submit')
  await page.waitForSelector('#modulemenu')
}

const isTaskCompleted = async (taskId) => {
  const taskUrl = `https://pm.hailiangtong.com/index.php?m=task&f=view&taskID=${taskId}`
  await page.goto(taskUrl)
  const selector = '.col-side .main-side fieldset:nth-child(1) > table tr'
  const data = await page.evaluate(context => {
    const label = document.querySelector(context.selector + ':nth-child(6)').querySelector('th').innerHTML.trim()
    const value = document.querySelector(context.selector + ':nth-child(6)').querySelector('td').innerHTML.trim()
    return {
      label,
      value
    }
  }, {
      selector
    })
  if (data.label !== '任务状态') {
    throw new Error('页面 DOM 结构改变，任务状态无法确定！')
  }
  return data.value === '已完成'
}

const listMyTasks = async (taskType, withMeta = false) => {
  await page.goto('https://pm.hailiangtong.com/index.php?m=my&f=task')
  const taskToStart = await page.evaluate(context => {
    return Array.from(document.querySelectorAll('table.table tbody tr'))
      .filter(tr => {
        let match = false
        tr.querySelectorAll('td')
          .forEach(td => {
            if (!context.taskType) {
              match = true
              return
            }
            if (td.getAttribute('class') && td.getAttribute('class').trim() === context.taskType) {
              match = true
            }
          })
        return match
      })
      .map(tr => {
        const id = tr.querySelector('.cell-id a').innerHTML
        if (context.withMeta) {
          return {
            id,
            taskName: tr.querySelectorAll('.text-left.nobr a')[1].innerHTML,
            state: tr.querySelectorAll('td')[11].innerHTML,
          }
        }
        return id
      })
  }, {
      assignedTo: config.name,
      taskType,
      withMeta
    })
  return taskToStart
}

////////////////////////////////// CREATE //////////////////////////////////
const createTask = async () => {
  notify('开始创建任务')

  const addTask = async (taskName, taskHour) => {
    if (!taskName) {
      notify('请输入任务名称！')
      return
    }
    if (isNaN(Number(taskHour))) {
      notify('任务预估时间应为数字！')
      return
    }
    taskHour = taskHour.toString()
    await fillInTaskForm(taskName, taskHour)
    const [taskId] = await listMyTasks(taskStateMap.await)
    await startTask(taskId)
    notify(`任务：${taskName}，时间：${taskHour}，创建完成！`)
  }

  if (argv.m) {
    const tasks = [...argv._]
    const tasksCount = tasks.length
    const hour = Math.round(8 / tasks.length)
    while (tasks.length !== 0) {
      const task = tasks.shift()
      await addTask(task, hour)
    }
    notify(`成功创建${tasksCount}个任务`)
    return
  }

  const taskName = argv._[0]
  let taskHour = argv._[1] || '8'
  await addTask(taskName, taskHour)
}

const fillInTaskForm = async (taskName, taskHour) => {
  await page.goto('https://pm.hailiangtong.com/index.php?m=project&f=task&projectID=38&type=unclosed')
  await wait(2000)
  await page.click('#createActionMenu a')
  await page.waitForSelector('#type')
  await page.select('#type', 'devel')
  await page.click('#assignedTo_chosen')
  const found = await page.evaluate(ctx => {
    const found = Array.from(document.querySelector('#assignedTo_chosen .chosen-results').childNodes)
      .find(element => {
        return element.childNodes[1].textContent.split(':')[1] === ctx.name
      })
    return found
      ? found.getAttribute('data-option-array-index')
      : null
  }, { name: config.name })

  if (!found) {
    throw new Error(`名字${config.name}未找到！`)
  }
  await page.click(`[data-option-array-index="${found}"]`)
  const assignedTo = await page.evaluate(() => {
    const text = document.querySelector('#assignedTo_chosen .chosen-single > span').innerHTML
    return text
  })
  if (assignedTo.substr(2) !== config.name) {
    throw new Error(`指派人错误！应为 ${config.name}，实际为 ${assignedTo}`)
  }
  await page.type('#name', taskTpl(taskName))
  await page.type('#estimate', taskHour)
  await page.click('#estStarted')
  const t = moment().format('Y-MM-DD')
  await page.type('#estStarted', t)
  const days = Math.ceil(taskHour / 8) - 1
  const t2 = moment().add(days, 'days').format('Y-MM-DD')
  await page.type('#deadline', t2)
  await page.click('#titlebar')
  await page.click('#titlebar')
  await page.click('#submit')
  await page.waitForNavigation({ waitUntil: 'networkidle0' })
}

const startTask = async (taskId) => {
  const taskUrl = `https://pm.hailiangtong.com/index.php?m=task&f=start&taskID=${taskId}`
  await page.goto(taskUrl)
  await page.click('#submit')
  // await page.waitForNavigation({ waitUntil: 'networkidle0' })
}

////////////////////////////////// CLOSE //////////////////////////////////
const closeTasks = async (taskIdInput) => {
  let taskIds
  if (taskIdInput) {
    taskIds = [taskIdInput]
  } else {
    taskIds = await listMyTasks(taskStateMap.onGoing)
  }
  try {
    while (taskIds.length !== 0) {
      const taskId = taskIds.pop()
      notify(`正在关闭任务 ${taskId}`)
      await closeTask(taskId)
      notify(`关闭任务 ${taskId} 成功`)
      // await withTimeoutRetry({
      //   fn: () => {
      //     return closeTask(taskId)
      //   },
      //   hint: `关闭任务${taskId}超时。`
      // })
    }
  } catch (error) {
    notify(error)
  } finally {
    const count = await listMyOnGoingTasks()
    if (count == 0) {
      notify('所有任务关闭成功')
    }
  }
}

const closeTask = async (taskId) => {
  const isAlreadyClosed = await isTaskCompleted(taskId)
  if (isAlreadyClosed) {
    notify(`任务${taskId}已经关闭！`)
    return
  }
  const taskUrl = `https://pm.hailiangtong.com/index.php?m=task&f=view&taskID=${taskId}`
  await page.goto(taskUrl)
  const dueDateSelector = '.col-side .main-side fieldset:nth-child(2) > table tr'
  // 检查截止日期是否是今天
  await page.waitForSelector(dueDateSelector)
  const [label, value, label2, value2] = await page.evaluate(context => {
    const l = document.querySelector(context.dueDateSelector + ':nth-child(3)').querySelector('th').innerHTML.trim()
    const v = document.querySelector(context.dueDateSelector + ':nth-child(3)').querySelector('td').innerHTML.trim()
    const l2 = document.querySelector(context.dueDateSelector + ':nth-child(4)').querySelector('th').innerHTML.trim()
    const v2 = document.querySelector(context.dueDateSelector + ':nth-child(4)').querySelector('td').innerHTML.trim()
    return [l, v, l2, v2]
  }, {
      dueDateSelector
    })
  if (label !== '截止日期') {
    throw new Error('页面 DOM 结构改变，截止日期无法确定！')
  }
  if (value !== moment().format('Y-MM-DD')) {
    notify(`任务${taskId}未到期（${value} 到期），无需关闭`)
    return
  }
  if (label2 !== '最初预计') {
    throw new Error('页面 DOM 结构改变，最初预计无法确定！')
  }
  await page.goto(`https://pm.hailiangtong.com/index.php?m=task&f=finish&taskID=${taskId}`)
  await page.waitForSelector('#submit')
  await page
    .evaluate(() => {
      document.querySelector('#consumed').value = ''
    })
  await page.type('#consumed', value2.replace('工时', ''))
  await page.evaluate(() => {
    $(document.querySelector('#submit')).click()
  })
  await wait(3000)
  // await page.waitForNavigation({ waitUntil: 'networkidle0' })
}

////////////////////////////////// LISTING //////////////////////////////////
const listMyOnGoingTasks = async ({ all = false } = {}) => {
  const filter = all ? null : taskStateMap.onGoing
  const tasks = await listMyTasks(filter, true)
  const info = tasks.map(task => {
    return `${task.id} ${task.state} ${task.taskName}`
  }).join('\n').trim()
  notify(info || '无进行中任务')
  return tasks.length
}

////////////////////////////////// COMMANDS //////////////////////////////////
const autoTask = async () => {
  try {
    await init()
    if (argv._[0] === 'close') {
      await closeTasks(argv._[1])
    } else if (argv._[0] === 'list') {
      await listMyOnGoingTasks({
        all: argv.all
      })
    } else {
      await createTask()
    }
  } catch (error) {
    notify(error.message || '发生未知错误！')
  } finally {
    await browser.close()
  }
}

autoTask()
